package jiang.jredis.lock;

import io.micrometer.core.instrument.util.StringUtils;
import jiang.jredis.lock.annotation.Lock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * 分布式锁AOP
 */
@Aspect
public class RedisLockAspect {

    private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
    private static final LocalVariableTableParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    /**
     * Redisson
     */
    private final RedissonClient redissonClient;

    /**
     * 构造方法 注入Redisson
     */
    public RedisLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 分布式锁AOP
     */
    @Around("@annotation(lockAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint, Lock lockAnnotation) throws Throwable {

        // 获取锁
        String lockKey = generateLockKey(joinPoint, lockAnnotation);
        RLock lock = redissonClient.getLock(lockKey);

        // 尝试加锁
        boolean locked = false;
        try {
            locked = lock.tryLock(lockAnnotation.waitTime(), lockAnnotation.leaseTime(), lockAnnotation.unit());
        } catch (InterruptedException e) {
        }

        // 加锁失败 抛异常
        if(locked == false) {
            throw new RuntimeException("@Lock注解加锁失败");
        }

        // 加锁成功 执行方法
        try {
            // 执行方法
            return joinPoint.proceed();
        } finally {
            // 解锁
            lock.unlock();
        }
    }

    /**
     * 生成 redis lock key
     * 格式为：lock:[name]:[key1],[key2]...
     */
    @SuppressWarnings("all")
    private String generateLockKey(ProceedingJoinPoint joinPoint, Lock lockAnnotation) {

        // 获取注解属性
        String name = lockAnnotation.name();
        String key = lockAnnotation.key();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 如果没有设置name 使用类名+方法名
        if(StringUtils.isEmpty(name)) {
            name = joinPoint.getTarget().getClass().getName() + "." + method.getName();
        }

        // 如果没有key 默认使用全部参数
        if(StringUtils.isEmpty(key)) {
            Object[] args = joinPoint.getArgs();
            key = Arrays.stream(args).map(Object::toString).collect(Collectors.joining(","));
            return "lock:" + name + ":" + key;
        }

        // 使用springEl解析key
        String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        // 获取方法参数值
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            // 替换表达式里的变量值为实际值，p0,p1或者原参数名
            evaluationContext.setVariable(parameterNames[i], args[i]);
            evaluationContext.setVariable("p" + i, args[i]);
        }
        try {
            // 解析key
            key = SPEL_PARSER.parseExpression(key).getValue(evaluationContext, String.class);
        } catch (RuntimeException e) {
            throw new EvaluationException("解析spEl表达式失败：" + name, e);
        }

        // 返回
        return "lock:" + name + ":" + key;
    }

}
